/* 
 * File:   Cliente.cpp
 * Author: Maximiliano Monzon (82341)
 * 
 * Created on October 20, 2012, 9:05 AM
 */
#include <cstdio>
#include <iostream>
#include <sstream>
#include "client_Cliente.h"
#include "client_Operacion.h"
#include "client_OperacionEco.h"
#include "client_OperacionCargaNuevoArchivo.h"
#include "common_ProtocoloConstantes.h"
#include "common_Excepcion.h"
#include "common_Descriptor.h"

using namespace std;
using namespace TP;
using namespace TP::Operaciones;
using namespace TP::Comunicacion;

Cliente::Cliente(ClientConfFileManager* pConfigurationManager, std::ostream& pLog): _log (pLog) {
    
    _config = pConfigurationManager;
    _socket = new SocketAsincronico (pConfigurationManager->GetIpServidor(), pConfigurationManager->GetPuertoServidor());
    _observadorSocket = new ObservadorSocket(_socket);
    _fileService = new FileServiceProxy(_socket);
    _ecoService = new EcoServiceProxy(_socket);
    _repositorio = NULL;
    _repositorioPoller = NULL;
}

Cliente::~Cliente() {
    
    delete _socket; 
    _socket = NULL;
    
    delete _observadorSocket;
    _observadorSocket = NULL;
    
    delete _repositorio;
    _repositorio = NULL;
    
    delete _repositorioPoller;
    _repositorioPoller = NULL;
    
    _config = NULL;
}

void Cliente::Iniciar() throw (Excepcion&){

    try
    {   
        /* Inicio la conexion con el servidor.
         */
        _socket->Iniciar();
        
        /* Ejecuto la autenticacion.
         */
        if (!this->Autenticar())
        {
            throw Excepcion ("Error de autenticacion.");
        }
        
        /* Inicio el repositorio.
         */
        this->ObtenerRepositorioServidor();
        
        _repositorioPoller = new RepositorioPoller(_repositorio, _config->GetIntervaloPolling());
        _repositorioPoller->setRepositorioPollerManejadorEvento(reinterpret_cast<IRepositorioPollerManejadorEvento*> (this));
        _repositorioPoller->Iniciar();
    
        
        _observadorSocket->SetObservador((IObservadorSocketManejadorEvento*) this);
        _observadorSocket->Iniciar();
    }
    catch (Excepcion &pEx)
    {
        pEx.expandirMensaje("No se puede inicar el cliente.");
        throw;
    }
}

void Cliente::Finalizar() throw (Excepcion&){
    try
    {
        _repositorioPoller->Finalizar();
        _observadorSocket->Finalizar();
        _socket->Finalizar();
    }
    catch (Excepcion& pEx)
    {
        pEx.expandirMensaje("No se puede finalizar el cliente.");
        throw;
    }
}

/* notificacionErrorConexion:
 *      Maneja los mensajes de error de conexion por parte del socket.
 */
void Cliente::notificarErrorConexion (IEnviante* pEnviante, const string& pMensaje){
    cout << endl << pMensaje << endl;
}

/* notificarMensajeEntrante:
 *      Maneja la notificacion de un mensaje entrante a traves del socket.
 */
void Cliente::notificarMensajeEntranteServidor(IEnviante* pEnviante, const string& pMensaje) {
    
    try
    {
        vector<string> mensaje = hidratarMensaje(pMensaje);
        vector<IRespuestaServidorManejador*>::iterator it = _respuestaServidorManejadores.begin();
        
        while (it < _respuestaServidorManejadores.end())
        {
            if ((*it)->asociado(mensaje))
            {
                (*it)->procesarRespuesta(mensaje);
                break;
            }            
            ++it;
        }
    }
    catch (Excepcion &ex)
    {
        cerr << endl << ex.what() << endl;
    }
}

vector<string> Cliente::hidratarMensaje(const string& pMensaje) throw (Excepcion&) {
        
    // Hidrato el comando.
    try
    {
        std::stringstream ssParametros (pMensaje);
        string strParametro = "";
        vector<string> _mensaje;
        
        while (std::getline(ssParametros, strParametro, SEPARADOR_CAMPO))
        {
            std::stringstream ssHelper;
            ssHelper << strParametro;
            strParametro = "";
            ssHelper >> strParametro;

            if (strParametro.length () > 0)
            {
                _mensaje.push_back(strParametro);
            }
        }
        
        return _mensaje;
    }
    catch (int &)
    {
        throw;
    }
}

/* archivoNuevoEvento:
 *      Maneje la notificacion de nuevo evento por parte del RepositorioPoller
 */
void Cliente::archivoNuevoEvento(const string & pNombreArchivo){
    
    /* RepositorioPoller notifica un nuevo archivo. Se declara en el repositorio
     * Y se notifica al servidor como nuevo archivo.
     */
    
    RepoItem * repoitem = new RepoItem();
    repoitem->SetFilePath(this->_repositorio->GetPath());
    repoitem->SetFilename(pNombreArchivo);
    this->_repositorio->AgregarRepoItem(repoitem);
    
    
    static int id = 0;
    
    //Operacion* operacion = (Operacion*) new OperacionEco(id++, _ecoService);
    
    Operacion* operacion = (Operacion*) new OperacionCargaNuevoArchivo(id++, _fileService, _repositorio, pNombreArchivo);
    _respuestaServidorManejadores.push_back((IRespuestaServidorManejador*) operacion);
    operacion->ejecutar();
}


/* archivoModificadoEvento.
 *      Maneja la notificacion de archivo modificado por parte del poller.
 */
void Cliente::archivoModificadoEvento(const string & pNombreArchivo){

    cout << endl << pNombreArchivo << endl;
}

bool Cliente::Autenticar(){
    
    bool autenticado = false;
    
    // Envia la credencial al servidor.
    string credencial = GetCredencial ();
    _socket->Escribir((uint8_t*) credencial.c_str(), credencial.length());

    // Obtiene la respuesta de autenticacion.
    
    uint8_t* buffer = NULL;
    uint32_t bufferLongitud = 0;
    uint32_t intentos = 10;
    
    while (0 == bufferLongitud && intentos > 0)
    {
        usleep (1000000);
        _socket->Leer(buffer, bufferLongitud);
        intentos--;
    }

    // Verifica resultado de autenticacion.
    if (bufferLongitud > 0)
    {
        string respuesta = "";
        for (size_t i = 0; i < bufferLongitud; i++) respuesta += buffer[i];
        Descriptor descriptor (SEPARADOR_CAMPO);
        descriptor.parse(respuesta);
        
        autenticado = (AUTENTICACION_OK == descriptor.data(0));
    }
    
    return autenticado;
}

string Cliente::GetCredencial (){
    
    string credencial = USUARIO_CREDENCIAL;
    
    credencial += SEPARADOR_CAMPO;
    credencial += _config->GetUsuario();
    credencial += SEPARADOR_CAMPO;
    credencial += _config->GetPassword();
    
    return credencial;
}

void Cliente::ObtenerRepositorioServidor () throw (Excepcion &) {

    _repositorio = new Repositorio();

}